package edu.utsa.cs.contactmanager;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.StringTokenizer;
import java.util.TreeMap;

import android.app.Activity;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.provider.ContactsContract;
import android.provider.ContactsContract.CommonDataKinds.Email;
import android.widget.Toast;

/**
 * The reason this class needs to be static is:
 *  1. SQL Databases can't be passed around in serializable format
 * @author user
 *
 */

@SuppressWarnings("serial")
public class ContactManager implements Serializable{
	
	private static TreeMap<Long, Contact> contactMap; //Current working list of contacts
	private static TreeMap<Long, Group> groupMap; //List of current working groups
	
	private static SQLiteDatabase database; //Database on disk
	private static ContactDatabaseHelper dbHelper; //Manager for db on disk
	
	/**
	 * This method MUST be called before open is
	 * @param context
	 */
	public static void initialize(Context context){
		dbHelper = new ContactDatabaseHelper(context);
	}
	
	/**
	 * Method to add a contact to the database and be displayed later
	 * @param contact - to be added
	 * @return true or false if the contact has been added
	 */
	public static boolean addContact(Contact contact){
		boolean added = false;
		
		
		if(contact.getID() == -1){
			contact = commitContactToDatabase(contact); //Sets the id field
			contactMap.put(contact.getID(), contact);
			
			ArrayList<String> nums = contact.getPhoneNums();
			for(String num : nums){
				if(num!=null && !num.equals("") && !num.equals(" "))
				addPhoneNumberToContactDatabase(contact.getID(), num);
			}
			
			added = true;
		}
		return added;
	}
	
	/**
	 * Method for removing a contact from the working set and database
	 * @param id for looking up the contact to remove
	 * @return true or false if the contact has been removed
	 */
	public static boolean removeContactById(long id){
		boolean removed = false;
		
		if(contactMap.containsKey(id)){
			contactMap.remove(id);
			deleteContactFromDatabase(id);
			removed = true;
		}
		
		return removed;
	}
	
	/**
	 * Update a contact that has already been added. If the contact is not found it will be added.
	 * @param contact - to be updated
	 * @return if the contact has been updated successfully
	 */
	public static boolean updateContact(Contact contact){
		
		if(contactMap.containsKey(contact.getID())){
			contactMap.put(contact.getID(), contact);
			updateContactInDatabase(contact);
			ArrayList<String> nums = contact.getPhoneNums();
			removePhoneNumberFromContactDatabase(contact.getID(), null);//Delete them all add em all again
			for(String num : nums){
				if(num!=null && !num.equals("") && !num.equals(" "))
				addPhoneNumberToContactDatabase(contact.getID(), num);
			}
			
			return true;
		}
		else
			return addContact(contact);
		
	}

	/**
	 * Add a group to the database
	 * @param group - to be added
	 * @return true or false if the Group was successfully added
	 */
	public static boolean addGroup(Group group){
		boolean add = true, added = false;
		
		if(group.getID() == -1){
			
			//Check to see if there are no duplicate names
			for(Map.Entry<Long, Group> entry : groupMap.entrySet()){
				if(entry.getValue().getName().equalsIgnoreCase(group.getName()))
					add = false;
			}
			if(add){
				group = commitGroupToDatabase(group);
				groupMap.put(group.getID(), group);
				added = true;
			}
		}
		return added;
	}
	
	/**
	 * Remove a group based on it's ID
	 * @param id of group to be removed
	 * @return true or false if the group has been removed.
	 */
	public static boolean removeGroupById(long id){
		boolean removed = false;
		
		if(groupMap.containsKey(id)){
			groupMap.remove(id);
			deleteGroupFromDatabase(id);
			removed = true;
		}
		
		return removed;
	}
	
	/**
	 * Updates a group that has already been added to the database. If not found, the group will be added.
	 * @param group to be updated
	 * @return true or false if the group was successfully updated/added
	 */
	public static boolean updateGroup(Group group){
		if(groupMap.containsKey(group.getID())){
			groupMap.put(group.getID(), group);
			updateGroupInDatabase(group);
			
			return true;
		}
		else
			return addGroup(group);
	}
	
	/**
	 * This method adds a contact to a group and updates the database
	 * @param groupID
	 * @param contactID
	 * @return true or false if the contact has been successfully added to the group.
	 */
	public static boolean addContactToGroup(long groupID, long contactID){
		Group g = groupMap.get(groupID);
		Contact c = contactMap.get(contactID);
		if(g!=null && c!=null){
			List<Contact> cInGroup = g.getMembers();
			for(Contact con : cInGroup){
				if(con.getID()==contactID)
					return false;
			}
			g.addMember(c);
			addContactToGroupDatabase(groupID, contactID);
			return true;
		}
		return false;
	}
	
	/**
	 * This method removes a contact from the group specified.
	 * @param groupID
	 * @param contactID
	 * @return success of function
	 */
	public static boolean removeContactFromGroup(long groupID, long contactID){
		Group g = groupMap.get(groupID);
		Contact c = contactMap.get(contactID);
		if(g!=null && c!=null){
			g.removeMember(c);
		}
		
		removeContactFromGroupDatabase(groupID, contactID);
		
		return true;
	}
	
	/**
	 * This method will import all contacts from the stock android app and check for duplicates
	 * @param activity
	 * @return success of the function call.
	 */
	public static boolean importContactsFromAndroidStockApp(Activity activity){
		ContentResolver cr = activity.getContentResolver();
        Cursor cur = cr.query(ContactsContract.Contacts.CONTENT_URI,null, null, null, null);

        if (cur.getCount() > 0) {
           while (cur.moveToNext()) {
               String id = cur.getString(cur.getColumnIndex(ContactsContract.Contacts._ID));
               String name = cur.getString(cur.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
               String phone = "";
               if (Integer.parseInt(cur.getString(cur.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER))) > 0) {

                   // get the phone number
                   Cursor pCur = cr.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI,null,
                                          ContactsContract.CommonDataKinds.Phone.CONTACT_ID +" = ?",
                                          new String[]{id}, null);
                   while (pCur.moveToNext()) {
                         phone = pCur.getString(pCur.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER));
                   }
                   pCur.close();
               }
               
               
               //For email: http://stackoverflow.com/questions/10599578/access-contacts-and-get-email-address
               
               boolean check = true;
               
               String first = "";
               String last = "";
               String[] tokens = name.split("\\s");
               if(tokens.length>0){
	               if(tokens[0]!=null){
	            	   first = tokens[0];
	               }
	               for(int i = 1; i < tokens.length; i++){
	            	   if(i+1 == tokens.length)
	            		   last = last.concat(tokens[i]);
	            	   else
	            		   last = last.concat(tokens[i] + " ");
	               }
		            	   
               }
               
               for(Map.Entry<Long, Contact> entry : contactMap.entrySet()){
            	   Contact c = entry.getValue();
            	   if((c.getFirstName().equalsIgnoreCase(first) && c.getLastName().equalsIgnoreCase(last))){// || (c.getPhoneNums()!=null && c.getPhoneNums().get(0)!=null && c.getPhoneNums().get(0).equalsIgnoreCase(phone))){
            		   check = false;
            		   break;
            	   }
               }
               if(check){
            	   Contact newContact = new Contact(first, last, null, null, false);//TODO build a better looking contact with more fields filled (email)
            	   newContact.addPhoneNum(phone);
            	   addContact(newContact);
               }
           }
        }
            	
		return true;
	}
	
	/**
	 * 
	 * @return all contacts available
	 */
	public static List<Contact> getContacts(){
		return new ArrayList<Contact>(contactMap.values());
	}
	

	/**
	 * 
	 * @param ids an array of Long Ids of contacts to be returned
	 * @return ArrayList<Contact> with the IDS specified
	 */
	public static ArrayList<Contact> getContacts(Long... ids){
		ArrayList<Contact> ret = new ArrayList<Contact>();
		for(Long i : ids){
			Contact c = contactMap.get(i);
			ret.add(c);
		}
		return ret;
	}
	
	/**
	 * 
	 * @return all groups available
	 */
	public static List<Group> getGroups(){
		ArrayList<Group> ret = new ArrayList<Group>();
		
		for(Map.Entry<Long, Group> entry : groupMap.entrySet()){
			ret.add(entry.getValue());
		}
		
		return ret;
	}
	
	/**
	 * 
	 * @param ids Long ids of all groups you want
	 * @return all groups with the ids specified
	 */
	public static ArrayList<Group> getGroups(Long... ids){
		ArrayList<Group> ret = new ArrayList<Group>();
		for(Long i : ids){
			Group g = groupMap.get(i);
			ret.add(g);
		}
		return ret;
	}
	
	/**
	 * Get a contact based on his phone number. Typically used for incomming calls
	 * @param num of contact to return
	 * @return Contact of num specified
	 */
	
	public static Contact getContactByPhoneNumber(String num){
		ArrayList<Contact> contacts = new ArrayList<Contact>(contactMap.values());
		ArrayList<String> numbers;
		for(Contact c : contacts){
			numbers = c.getPhoneNums();
			for(String n : numbers){
				if(n.equals(num)){
					return c;
				}
			}
		}
		return null;
	}
	
	/**
	 * DB Items here
	 */
	
	/**
	 * This method imports all contacts from an exported database. It does not import groups or check for doubles, like most phones
	 * @param context
	 * @param file File name that contains an exported database
	 */
	public static void importFromFile(Context context, String file){
	    ContactDatabaseHelper dbh = new ContactDatabaseHelper(context, file);
	    SQLiteDatabase db = dbh.getReadableDatabase();
	    TreeMap<Long, Contact> cMap;
	    
	    cMap = loadContactsFromDatabase(db);
	    
	    db.close();
	    
	    Contact c;
	    for(Map.Entry<Long, Contact> e: cMap.entrySet()){
	    	c = e.getValue();
	    	c.setID(-1);//Doesn't exist now
	        addContact(c);
	    }
	    
	}
	
	/**
	 * This method must be called before reading or writing to the database
	 * @throws SQLException
	 */
	public static void open() throws SQLException{
	    database = dbHelper.getWritableDatabase();
	    contactMap = loadContactsFromDatabase(database);
	    groupMap = loadGroupsFromDatabase(database, contactMap);
	}
	
	/**
	 * Safely closes the database that has been written to.
	 */
	public static void close(){
		dbHelper.close();
	}
	
	/**
	 * Database function for writing contact to disk
	 * @param contact
	 * @return
	 */
	private static Contact commitContactToDatabase(Contact contact){
		
		ContentValues values = new ContentValues();
		values.put(ContactDatabaseHelper.FIELD_CONTACT_FIRST_NAME, contact.getFirstName());
		values.put(ContactDatabaseHelper.FIELD_CONTACT_LAST_NAME, contact.getLastName());
		values.put(ContactDatabaseHelper.FIELD_CONTACT_EMAIL, contact.getEmail());
		values.put(ContactDatabaseHelper.FIELD_CONTACT_PICTURE, contact.getPicture());
		
		int black = 0;
		if(contact.isBlackListed())
			black = 1;
		
		values.put(ContactDatabaseHelper.FIELD_CONTACT_BLACKLIST, black);
		
		long id = database.insert(ContactDatabaseHelper.CONTACT_TABLE_NAME, null, values);
		
		contact.setID(id);
		
		return contact;
	}
	
	private static void updateContactInDatabase(Contact contact){
		
		ContentValues values = new ContentValues();
		values.put(ContactDatabaseHelper.FIELD_CONTACT_FIRST_NAME, contact.getFirstName());
		values.put(ContactDatabaseHelper.FIELD_CONTACT_LAST_NAME, contact.getLastName());
		values.put(ContactDatabaseHelper.FIELD_CONTACT_EMAIL, contact.getEmail());
		values.put(ContactDatabaseHelper.FIELD_CONTACT_PICTURE, contact.getPicture());
		
		int black = 0;
		if(contact.isBlackListed())
			black = 1;
		
		values.put(ContactDatabaseHelper.FIELD_CONTACT_BLACKLIST, black);
		
		database.update(ContactDatabaseHelper.CONTACT_TABLE_NAME, values, ContactDatabaseHelper.FIELD_CONTACT_ID + " = " + contact.getID(), null);
	}

	private static void deleteContactFromDatabase(long id){
		database.delete(ContactDatabaseHelper.CONTACT_TABLE_NAME, ContactDatabaseHelper.FIELD_CONTACT_ID + 
				" = " + id, null);
	}
	
	private static TreeMap<Long, Contact> loadContactsFromDatabase(SQLiteDatabase db){
		
	    TreeMap<Long, Contact> cMap = new TreeMap<Long, Contact>();
		Cursor cursor = db.query(ContactDatabaseHelper.CONTACT_TABLE_NAME, ContactDatabaseHelper.allContactColumns, null, null, null, null, null);
		cursor.moveToFirst();
		while(!cursor.isAfterLast()){
			Contact c = cursorToContact(cursor);
			cMap.put(c.getID(), c);
			cursor.moveToNext();
		}
		cursor.close();
		
		for(Map.Entry<Long, Contact> c : cMap.entrySet()){
			cursor = db.query(ContactDatabaseHelper.PEOPLES_PHONES_TABLE_NAME, ContactDatabaseHelper.allPeoplesPhoneColumns, ContactDatabaseHelper.FIELD_CONTACT_ID + " = " + c.getValue().getID(), null, null, null, null);
			cursor.moveToFirst();
			while(!cursor.isAfterLast()){
				String phoneNum = cursor.getString(2);
				if(phoneNum!=null){
					c.getValue().addPhoneNum(phoneNum);
				}
				cursor.moveToNext();
			}
			cursor.close();
		}
		
		return cMap;
	}
	
	private static void addPhoneNumberToContactDatabase(long contactID, String number){
		ContentValues values = new ContentValues();
		values.put(ContactDatabaseHelper.FIELD_CONTACT_ID, contactID);
		values.put(ContactDatabaseHelper.FIELD_CONTACT_PHONE_NUMBER, number);
		
		database.insert(ContactDatabaseHelper.PEOPLES_PHONES_TABLE_NAME, null, values);
	}
	
	private static void removePhoneNumberFromContactDatabase(long contactID, String number){
		
		if(number!=null)
			database.delete(ContactDatabaseHelper.PEOPLES_PHONES_TABLE_NAME,ContactDatabaseHelper.FIELD_CONTACT_ID + " = " + contactID + " and " + ContactDatabaseHelper.FIELD_CONTACT_PHONE_NUMBER + " = " + number, null);
		else
			database.delete(ContactDatabaseHelper.PEOPLES_PHONES_TABLE_NAME,ContactDatabaseHelper.FIELD_CONTACT_ID + " = " + contactID, null);
		
	}
	
	private static Contact cursorToContact(Cursor cursor){
		Contact contact = new Contact();
		contact.setID(cursor.getLong(0));
		contact.setFirstName(cursor.getString(1));
		contact.setLastName(cursor.getString(2));
		contact.setEmail(cursor.getString(3));
		contact.setPicture(cursor.getString(4));
		if(cursor.getInt(5)>0)
			contact.setBlackListed(true);
		else
			contact.setBlackListed(false);
		return contact;
	}
	
	
	
	private static Group commitGroupToDatabase(Group group){
		
		ContentValues values = new ContentValues();
		values.put(ContactDatabaseHelper.FIELD_GROUP_NAME, group.getName());	
		
		long id = database.insert(ContactDatabaseHelper.GROUP_TABLE_NAME, null, values);
		
		group.setID(id);
		return group;
	}
	
	private static void updateGroupInDatabase(Group group){
		
		ContentValues values = new ContentValues();
		values.put(ContactDatabaseHelper.FIELD_GROUP_NAME, group.getName());	
		
		database.update(ContactDatabaseHelper.GROUP_TABLE_NAME, values, ContactDatabaseHelper.FIELD_GROUP_ID + " = " + group.getID(), null);
	}
	
	private static void deleteGroupFromDatabase(long id){
		database.delete(ContactDatabaseHelper.GROUP_TABLE_NAME, ContactDatabaseHelper.FIELD_GROUP_ID + 
				" = " + id, null);
	}
	
	private static TreeMap<Long, Group> loadGroupsFromDatabase(SQLiteDatabase db, TreeMap<Long, Contact> cMap){
		
	    TreeMap<Long, Group> gMap = new TreeMap<Long, Group>();
		Cursor cursor = db.query(ContactDatabaseHelper.GROUP_TABLE_NAME, ContactDatabaseHelper.allGroupColumns, null, null, null, null, null);
		cursor.moveToFirst();
		while(!cursor.isAfterLast()){
			Group g = cursorToGroup(cursor);
			gMap.put(g.getID(), g);
			cursor.moveToNext();
		}
		cursor.close();
		
		
		for(Map.Entry<Long, Group> g : gMap.entrySet()){
			cursor = db.query(ContactDatabaseHelper.GROUPS_PEOPLE_TABLE_NAME, ContactDatabaseHelper.allGroupsPeopleColumns, ContactDatabaseHelper.FIELD_GROUP_ID + " = " + g.getValue().getID(), null, null, null, null);
			cursor.moveToFirst();
			while(!cursor.isAfterLast()){
				long id = cursor.getLong(1);
				Contact mem = cMap.get(id);
				if(mem!=null){
					g.getValue().addMember(mem);
				}
				cursor.moveToNext();
			}
			cursor.close();
		}
		
		return gMap;
		
	}
	
	private static void addContactToGroupDatabase(long groupID, long contactID){
		ContentValues values = new ContentValues();
		values.put(ContactDatabaseHelper.FIELD_GROUP_ID, groupID);
		values.put(ContactDatabaseHelper.FIELD_CONTACT_ID, contactID);
		
		database.insert(ContactDatabaseHelper.GROUPS_PEOPLE_TABLE_NAME, null, values);
	}
	
	/**
	 * Special cases for groupID or contactID == -2 will remove all instances.
	 * @param groupID
	 * @param contactID
	 */
	private static void removeContactFromGroupDatabase(long groupID, long contactID){
		if(contactID == -2){
			database.delete(ContactDatabaseHelper.GROUPS_PEOPLE_TABLE_NAME, ContactDatabaseHelper.FIELD_GROUP_ID + " = " + groupID, null);
		}
		else if(groupID == -2){
			database.delete(ContactDatabaseHelper.GROUPS_PEOPLE_TABLE_NAME, ContactDatabaseHelper.FIELD_CONTACT_ID + " = " + contactID, null);
		}
		else
			database.delete(ContactDatabaseHelper.GROUPS_PEOPLE_TABLE_NAME, ContactDatabaseHelper.FIELD_GROUP_ID + " = " + groupID + " and " + ContactDatabaseHelper.FIELD_CONTACT_ID + " = " + contactID, null);
	}
	
	private static Group cursorToGroup(Cursor cursor){
		Group group = new Group("");
		group.setID(cursor.getLong(0));
		group.setName(cursor.getString(1));
		return group;
	}
	
	
}
